package com.diy.sigmund.diyoauth2.common.spring.config;

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.JdbcClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

/**
 * @author ylm-sigmund
 * @since 2021/8/23 16:23
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;
    @Autowired
    public UserDetailsService customUserDetailsService;
    @Autowired
    private AuthenticationManager authenticationManagerBean;
    @Resource
    private TokenStore redisTokenStore;
    @Resource
    private TokenStore customJwtTokenStore;
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Autowired
    private TokenEnhancer customJwtTokenEnhancer;
    @Autowired
    private DataSource dataSource;

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // jwt 增强模式
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancerList = new ArrayList<>();
        enhancerList.add(customJwtTokenEnhancer);
        enhancerList.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(enhancerList);

        // 过期时间可修改oauth_client_details表内每一条client信息的access_token_validity
        endpoints.tokenStore(customJwtTokenStore)
                // 指定 token 的存储方式。
                // .tokenStore(redisTokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(enhancerChain)
                // 设置用户验证服务。
                .userDetailsService(customUserDetailsService)
                // 调用此方法才能支持 password 模式。
                .authenticationManager(authenticationManagerBean);

        // 改行代码会覆盖前面的配置
        // endpoints.tokenServices(defaultTokenServices());

        // 普通 jwt 模式
        /*endpoints.tokenStore(jwtTokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .userDetailsService(customUserDetailsService)
                .authenticationManager(authenticationManager);*/

        // redis token 方式
        /*endpoints.authenticationManager(authenticationManager)
                .tokenStore(redisTokenStore)
                .userDetailsService(customUserDetailsService);*/
    }

    /**
     * 需要endpoints手动设置才会生效，但是会覆盖endpoints其它的配置
     * <p>注意，自定义TokenServices的时候，需要设置@Primary，否则报错，</p>
     *
     * @return DefaultTokenServices
     */
    // @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(customJwtTokenStore);
        tokenServices.setSupportRefreshToken(true);
        // 设置1小时失效
        tokenServices.setAccessTokenValiditySeconds(60 * 70);
        // 设置低于失效时间的刷新时间
        tokenServices.setRefreshTokenValiditySeconds(60 * 30);
        return tokenServices;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
        jcsb.passwordEncoder(passwordEncoder);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 允许客户端访问 OAuth2 授权接口，否则请求 token 会返回 401
        security.allowFormAuthenticationForClients();
        // 允许已授权用户访问 checkToken 接口
        security.checkTokenAccess("isAuthenticated()");
        // 允许已授权用户访问 获取 token 接口
        security.tokenKeyAccess("isAuthenticated()");
    }

    /**
     * inMemory 方式存储的，将配置保存到内存中，相当于硬编码
     *
     * ClientId、Client-Secret：这两个参数对应请求端定义的 cleint-id 和 client-secret
     *
     * authorizedGrantTypes 可以包括如下几种设置中的一种或多种：
     * authorization_code：授权码类型。
     * implicit：隐式授权类型。
     * password：资源所有者（即用户）密码类型。
     * client_credentials：客户端凭据（客户端ID以及Key）类型。
     * refresh_token：通过以上授权获得的刷新令牌来获取新的令牌。
     *
     * accessTokenValiditySeconds：token 的有效期
     * scopes：用来限制客户端访问的权限，在换取的 token 的时候会带上 scope 参数，只有在 scopes 定义内的，才可以正常换取 token。
     *
     * @param clients clients
     * @throws Exception Exception
     */
    /*@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("order-client")
                .secret(passwordEncoder.encode("order-secret-8888"))
                .authorizedGrantTypes("refresh_token", "authorization_code", "password")
                .accessTokenValiditySeconds(3600)
                .scopes("all")
                .and()
                .withClient("user-client")
                .secret(passwordEncoder.encode("user-secret-8888"))
                .authorizedGrantTypes("refresh_token", "authorization_code", "password")
                .accessTokenValiditySeconds(3600)
                .scopes("all");
    }*/
}